﻿/******************************************************************************
 * Copyright (C) 671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "AuthSession.h"
#include <boost/unordered_map.hpp>
#include <NodeCpp/AutoPtr.h>
#include <NodeCpp/Util/Application.h>
#include <NodeCpp/Crypto/SRP6.h>
#include "Shared/Log/Log.h"
#include "GlobalVars.h"
#include "Game/SharedDefines.h"
#include "World/AddonHandler.h"
#include "World/World.h"
#include "WorldSocketDelegate.h"
#include "AuthSessionManager.h"
#include "WorldSession.h"
using NodeCpp::AutoPtr;
using NodeCpp::Crypto::SRP6;

#define s_BYTE_SIZE 32

class AuthQueryHolder : public SqlQueryHolder
{
public:
    enum QueryIndex
    {
        QUERY_INDEX_ACCOUNT = 0,
        QUERY_INDEX_ACCOUNT_BANNED,
        MAX_QUERY
    };

    AuthQueryHolder(int _ID) : ID_(_ID) { }
    ~AuthQueryHolder(void) { }

    int GetID(void) const { return ID_; }

private:
    int ID_;
};

class AuthHandler
{
public:
    void HandleAuthSessionAccountCallback(QueryResult * /*dummy*/, SqlQueryHolder* _Holder)
    {
        AutoPtr<SqlQueryHolder> _HolderPtr(_Holder);
        AuthSession* _Session = sAuthSessionMgr.FindSession(((AuthQueryHolder*)_Holder)->GetID());
        _Session->HandleAuthSessionAccount(_Holder);
    }
}G_AuthHandler_;

AuthSession::AuthSession(WorldSocketDelegate* _Sock)
    : Sock_(_Sock)
    , Packets_()
    , Running_(true)

    , PeerID_(0)
    , PeerHost_()
    , PeerPort_(0)

    , Authed_(false)

    , AddonPacket_()
{
    PeerID_ = Sock_->GetPeerID();
    PeerHost_ = Sock_->GetPeerHost();
    PeerPort_ = Sock_->GetPeerPort();

    // 未通过验证可以存活
    ::time(&UnauthLifeTime_);
    UnauthLifeTime_ += sAuthSessionMgr.GetMaxUnauthedTime();
    UnauthLifeTime_ *= 1000;
}

AuthSession::~AuthSession(void)
{
    _Uninitialize();
}

void AuthSession::SendAuthChallenge(std::uint32_t _Seed)
{
    WorldPacket _Packet(SMSG_AUTH_CHALLENGE, 4);
    _Packet << _Seed;
    Sock_->SendPacket(_Packet);
}

void AuthSession::QueuePacket(WorldPacket* _Packet) {
    Packets_.Add(_Packet);
}

bool AuthSession::Update(time_t _Diff)
{
    if (Authed_) {
        // 连接已通过验证，创建游戏会话，释放验证会话
        WorldSession* _WorldSession = new WorldSession(AccountId_, 
            Sock_, AccountTypes(Security_), MuteTime_, Locale_);
        if (_WorldSession != NULL) {
            Sock_->SetWorldSession(_WorldSession);
            sWorld.AddSession(_WorldSession);
            return false;
        }

        // 创建游戏会话失败，关闭会话
        Authed_ = false;
        Running_ = false;
    }

    if (!Running_) {
        // 必须先关闭底层网络连接
        return !Sock_->TryClose();
    }

    do
    {
        // 验证超时
        if (!Authed_) {
            UnauthLifeTime_ -= _Diff;
            if (UnauthLifeTime_ < 0) {
                Running_ = false;
                break;
            }
        }

        // 服务器正在关闭或网络出错
        if (!sAuthSessionMgr.IsRunning()
            || Sock_->IsClosing()) {
                Running_ = false;
                break;
        }

        // 处理数据包
        WorldPacket* _Pkt;
        while (Packets_.Next(_Pkt)) {
            if (!HandlePacket(_Pkt)) {
                Running_ = false;
                break;
            }
        }
    } while (false);

    return true;
}

bool AuthSession::HandlePacket(WorldPacket* _Pkt)
{
    /* 本会话只能处理（CMSG_AUTH_SESSION）消息 */
    AutoPtr<WorldPacket> _ReqPkt(_Pkt);
    switch(_Pkt->opcode())
    {
    case CMSG_AUTH_SESSION:
        sLog.outDebug("(%d) Received CMSG_AUTH_SESSION", PeerID_);
        return _HandleAuthSession(*_Pkt);
    default:
        return false;
    }
}

bool AuthSession::_HandleAuthSession(WorldPacket& _Pkt)
{
    /* 读取客户端发来的数据 */
    _Pkt >> BuiltNumberClient_;     // 客户端版本
    _Pkt >> Unk2_;                  //
    _Pkt >> Account_;               // 玩家帐号
    _Pkt >> ClientSeed_;            // 客户端随机种子
    _Pkt.Read(Digest_, 20);         // 客户端验证数据

    sLog.outDebug("client %u, unk2 %u, account %s, clientseed %u",
        BuiltNumberClient_,
        Unk2_,
        Account_.c_str(),
        ClientSeed_);

    /* 检查客户端版本是否匹配 */
    if (!IsAcceptableClientBuild(BuiltNumberClient_))
    {
        WorldPacket _RespPkt;
        _RespPkt.Initialize(SMSG_AUTH_RESPONSE, 1);
        _RespPkt << std::uint8_t(AUTH_VERSION_MISMATCH);
        Sock_->SendPacket(_RespPkt);
        sLog.outDebug("Sent Auth Response (version mismatch).");
        return false;
    }

    AddonPacket_.reset(new WorldPacket);
    if (!AddonHandler::BuildAddonPacket(&_Pkt, AddonPacket_.get())) {
        AddonPacket_.reset();
    }

    /* 查询玩家帐号信息/帐号禁止登录信息 */
    std::string _SafeAccount = Account_;
    sLoginDB.EscapeString(_SafeAccount);

    AutoPtr<AuthQueryHolder> _QueryHolder(new AuthQueryHolder(PeerID_));
    if (!_QueryHolder) {
        return false;
    }
    _QueryHolder->SetSize(AuthQueryHolder::MAX_QUERY);

    bool _SetQueryRes = _QueryHolder->SetPQuery(AuthQueryHolder::QUERY_INDEX_ACCOUNT,
        "SELECT "
        "id, "                      // 0 编号
        "gmlevel, "                 // 1 帐号等级
        "sessionkey, "              // 2 会话KEY
        "last_ip, "                 // 3 最后登录IP
        "locked, "                  // 4 是否被锁定
        "v, "                       // 5 SRP6:v
        "s, "                       // 6 SRP6:s
        "mutetime, "                // 7
        "locale "                   // 8 地区
        "FROM account "
        "WHERE username = '%s'",
        _SafeAccount.c_str());
    _SetQueryRes &= _QueryHolder->SetPQuery(AuthQueryHolder::QUERY_INDEX_ACCOUNT_BANNED,
        "SELECT 1 FROM account_banned WHERE account = '%s' AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)"
        "UNION "
        "SELECT 1 FROM ip_banned WHERE (unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'",
        _SafeAccount.c_str(), PeerHost_.c_str());

    if (!_SetQueryRes) {
        return false;
    }

    sLoginDB.DelayQueryHolder(&G_AuthHandler_, &AuthHandler::HandleAuthSessionAccountCallback, _QueryHolder.release());
    return true;
}

void AuthSession::HandleAuthSessionAccount(SqlQueryHolder* _Holder)
{
    AuthQueryHolder* _QueryHolder = (AuthQueryHolder*)_Holder;
    WorldPacket _RespPkt;

    /* 检查帐号是否存在 */
    QueryResult* _AccountResult = _QueryHolder->GetResult(AuthQueryHolder::QUERY_INDEX_ACCOUNT);
    if (!_AccountResult)
    {
        _RespPkt.Initialize(SMSG_AUTH_RESPONSE, 1);
        _RespPkt << std::uint8_t(AUTH_UNKNOWN_ACCOUNT);
        Sock_->SendPacket(_RespPkt);
        sLog.outError("Sent Auth Response (unknown account).");
        return;
    }

    Field* _Fields = _AccountResult->Fetch();

    N_.SetHexStr(SRP6::N);
    g_.SetUInt32(SRP6::g);

    v_.SetHexStr(_Fields[5].c_str());
    s_.SetHexStr(_Fields[6].c_str());

    const char* _SHex = s_.AsHexStr();
    const char* _VHex = v_.AsHexStr();
    sLog.outDebug("(s,v) check s: %s v: %s",
        _SHex,
        _VHex);
    BigNumber::Free((void*)_SHex);
    BigNumber::Free((void*)_VHex);

    if (_Fields[4].AsUInt8() == 1)
    {
        if (strcmp(_Fields[3].c_str(), PeerHost_.c_str()))
        {
            _RespPkt.Initialize(SMSG_AUTH_RESPONSE, 1);
            _RespPkt << std::uint8_t(AUTH_FAILED);
            Sock_->SendPacket(_RespPkt);
            sLog.outDebug("Sent Auth Response (Account IP differs).");
            return;
        }
    }

    // 帐号ID和帐号等级
    AccountId_ = _Fields[0].AsUInt32();
    Security_ = _Fields[1].AsUInt16();
    if (Security_ > SEC_ADMINISTRATOR) {
        Security_ = SEC_ADMINISTRATOR;
    }

    // 会话KEY
    K_.SetHexStr(_Fields[2].c_str());

    MuteTime_ = time_t(_Fields[7].AsUInt64());

    // 区域
    Locale_ = LocaleConstant(_Fields[8].AsUInt8());
    if (Locale_ >= MAX_LOCALE) {
        Locale_ = LOCALE_enUS;
    }

    /* 检查帐号是否被禁止登录 */
    QueryResult* _AccountBannedResult = _QueryHolder->GetResult(AuthQueryHolder::QUERY_INDEX_ACCOUNT_BANNED);
    if (_AccountBannedResult)
    {
        _RespPkt.Initialize(SMSG_AUTH_RESPONSE, 1);
        _RespPkt << std::uint8_t(AUTH_BANNED);
        Sock_->SendPacket(_RespPkt);
        sLog.outError("Sent Auth Response (Account banned).");
        return;
    }

    /* 验证 */
    Sha1Hash _Sha;

    std::uint32_t _T = 0;
    std::uint32_t _Seed = Sock_->GetSeed();

    _Sha.UpdateData(Account_);
    _Sha.UpdateData((std::uint8_t*) & _T, 4);
    _Sha.UpdateData((std::uint8_t*) & ClientSeed_, 4);
    _Sha.UpdateData((std::uint8_t*) & _Seed, 4);
    _Sha.UpdateBigNumbers(&K_, NULL);
    _Sha.Finalize();

    if (memcmp(_Sha.digest(), Digest_, 20))
    {
        _RespPkt.Initialize(SMSG_AUTH_RESPONSE, 1);
        _RespPkt << std::uint8_t(AUTH_FAILED);
        Sock_->SendPacket(_RespPkt);
        sLog.outError("Sent Auth Response (authentification failed).");
        return;
    }

    sLog.outDebug("Client '%s' authenticated successfully from %s.",
        Account_.c_str(),
        PeerHost_.c_str());

    /* 更新玩家最后登录IP */
    static SqlStatementID updateLastIp;
    SqlStatement stmt = sLoginDB.CreateStatement(updateLastIp, 
        "UPDATE account SET last_ip = ? WHERE username = ?");
    stmt.PExecute(PeerHost_.c_str(), Account_.c_str());

    /* 设置数据包加解密 */
    Sock_->InitAuthCrypt(K_);

    if (AddonPacket_) {
        Sock_->SendPacket(*(AddonPacket_.get()));
        AddonPacket_.reset();
    }

    /* 验证成功 */
    Authed_ = true;
}

void AuthSession::_Uninitialize(void)
{
    FREE_QUEUED_PACKETS(Packets_, WorldPacket);
}